﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using OpenLS.Drawing;
using OpenLS.Spreadsheet.Drawing;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats.Biff.OfficeArt;
using OpenLS.Spreadsheet.Formats.Biff.Records;
using OpenLS.Spreadsheet.Functions;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.StandardAddIn;
using Expression = OpenLS.Spreadsheet.Expressions.Expression;
using OpenLS.Spreadsheet.Collections;

namespace OpenLS.Spreadsheet.Formats.Biff
{
    class WorkbookSequencer: Sequencer
    {
        public  IEnumerable<BiffRecord> GetWorkbookRecords(BiffWriterWorkbookContext context)
        {
            this.context = context;
            return Concat(GetWorkbookGlobals(), GetSheetsRecords());
        }

        private  IEnumerable<object> GetSheetsRecords()
        {
            return Enumerable.SelectMany(context.Workbook.Sheets, sheet => GetSheetRecords(sheet));
        }

        private  IEnumerable<object> GetSheetRecords( Sheet sheet)
        {
            if (sheet is Chartsheet)
                return new BiffFormat[0];
            WorksheetSequencer worksheetSequencer = new WorksheetSequencer(context, (Worksheet)sheet);
            return worksheetSequencer.GetWorksheetRecords();
        }


        public IEnumerable<BiffRecord> GetWorkbookGlobals()
        {
            return Concat(new BofRecord(BofRecord.BofRecordType.Workbook), GetWorkbookContent());
        }

        private  IEnumerable<object> GetWorkbookContent()
        {
            yield return Interface();
            yield return new WriteAccessRecord(Environment.UserName);
            yield return new CodePageRecord();
            yield return new DsfRecord();
            //\\
            yield return new Excel9FileRecord();
            yield return new RrTabIdRecord();
            string name = context.Workbook.Name ?? "ThisWorkbook";
            yield return new CodeNameRecord(name);
            yield return new BuiltInFnGroupCountRecord();
            //\\
            // lbls
            yield return Protection();
            yield return new Window1Record(context.Workbook);
            yield return new BackupRecord();
            yield return new HideObjRecord();
            yield return new Date1904Record();
            yield return new CalcPrecisionRecord();
            yield return new RefreshAllRecord();
            yield return new BookBoolRecord();
            yield return Formatting();
            //\\pivotcachedef
            //\\DOCROUTE;
            //\\UserBView
            yield return BundleSheets();
            yield return Metadata();
            //\\MTRSettings] 
            //\\ForceFullCalculation]
            yield return new CountryRecord(); //\\
            yield return AddIns();
            yield return SupBook();
            //\\SUPBOOK 
            yield return Lbl();
            //\\  *RTD
            //\\ [RecalcId] HFPicture 
            //MSODRAWINGGROUP
            yield return GetMsoDrawingGroup();
            //SHAREDSTRINGS
            List<XLUnicodeRichExtendedString> strings = new List<XLUnicodeRichExtendedString>(GetXlStrings());
            bool sst = true;
            List<XLUnicodeRichExtendedString> currentList = new List<XLUnicodeRichExtendedString>();
            int currentLength = 0;
            foreach (var s in strings)
            {
                if (currentLength + s.Length > 8000)
                {
                    if (sst)
                    {
                        sst = false;
                        yield return new SstRecord(strings.Count, currentList);
                        ;
                    }
                    else
                    {
                        yield return new ContinueSSTRecord(currentList);
                    }
                    currentLength = s.Length;
                    currentList = new List<XLUnicodeRichExtendedString>(new[] { s });

                }
                else
                {
                    currentList.Add(s);
                    currentLength += s.Length;
                }
            }
            if (currentList.Count > 0 | sst)
            {
                if (sst)
                {
                    sst = false;
                    yield return new SstRecord(strings.Count, currentList);
                }
                else
                {
                    yield return new ContinueSSTRecord(currentList);
                }
            }
            //yield return new SstRecord(strings);
            yield return new ExtSstRecord();
            //\\*WebPub [WOpt] [CrErr] [BookExt]
            //\\ *FeatHdr *DConn
            //\\ [THEME]
            if (context.Workbook.Theme != null)
                yield return Theme(context.Workbook);
            //\\ [CompressPictures] [Compat12] [GUIDTypeLib] 
            yield return new EofRecord();

        }

        private  IEnumerable<BiffRecord> GetMsoDrawingGroup()
        {
//#if COMMENTS

            List<Cell> cellsWithComment = Enumerable.ToList(Enumerable.Where(context.Workbook.GetCells(), c => c.Comment != null));
            var pictures = Enumerable.ToList(Enumerable.SelectMany(Enumerable.OfType<Worksheet>(context.Workbook.Sheets),
                                                 ws => WorksheetSequencer.GetPictures(ws, context.PictureContext)));
            
            if (cellsWithComment.Count > 0 || pictures.Count > 0)
            {
                OfficeArtDggContainer container = new OfficeArtDggContainer();
                container.DrawingGroup = new OfficeArtFDGGBlock();
                container.DrawingGroup.Prepare();
                container.DrawingPrimaryOptions = new OfficeArtFOPT();
                container.DrawingPrimaryOptions.AddProperty(OpId.TextBooleanProperties, TextBooleanProperties.fAutoTextMargin | TextBooleanProperties.fUsefAutoTextMargin);
                container.DrawingPrimaryOptions.AddProperty(OpId.FillColor, OfficeArtCOLORREF.SchemeColor(65));
                container.DrawingPrimaryOptions.AddProperty(OpId.FillBlip, 0);
                container.DrawingPrimaryOptions.AddProperty(OpId.LineColor, OfficeArtCOLORREF.SchemeColor(64));
                container.DrawingPrimaryOptions.AddProperty(OpId.LineFillBlip,0);
                if (pictures.Count > 0)
                {
                    var blips = Enumerable.Select(pictures, picture => picture.BlipFillProperties.Blip);
                    container.BlipStore = new OfficeArtBStoreContainer(Enumerable.Select( blips, blip => new OfficeArtFBSE(OfficeArtBlip.Create(blip))));
                    foreach (var blip in blips)
                    {
                        context.AddBlip(blip);
                    }
/*                    container.BlipStore =
                        new OfficeArtBStoreContainer(Enumerable.Select(pictures,
                                                                       picture =>
                                                                       new OfficeArtFBSE(
                                                                           OfficeArtBlip.Create(
                                                                               picture.BlipFillProperties.Blip))));*/
                }
                byte[] bytes = container.GetBytesNoHeader();
                foreach (var item in ContinueRecordHelper.GetRecords((i, b) => new MsoDrawingGroupRecord(b), bytes))
                    yield return item;

            }

#if MSODRAWING
            /*
            yield break;
            var pictures = 
                Enumerable.ToList(
                Enumerable.OfType<Picture>(Enumerable.Select(Enumerable.SelectMany(Enumerable.OfType<Worksheet>(context.Workbook.Sheets), ws => ws.Anchors), a => a.Content)));
            if (pictures.Count == 0)
                yield break;
            OfficeArtDggContainer container = new OfficeArtDggContainer();
            container.DrawingGroup = new OfficeArtFDGGBlock();
            container.DrawingPrimaryOptions = new OfficeArtFOPT();
            container.BlipStore = new OfficeArtBStoreContainer(Enumerable.Select(pictures, picture => new OfficeArtFBSE(OfficeArtBlip.Create (picture.BlipFillProperties.Blip))));
            byte[] bytes = container.GetBytesNoHeader();
            foreach (var item in ContinueRecordHelper.GetRecords((i, b) => new MsoDrawingGroupRecord(b), bytes))
                yield return item;*/
#else
#endif
        }


        private  BiffRecord Theme(Workbook workbook)
        {
            Contract.Requires(workbook != null);
            return new ThemeRecord(workbook.Theme);
        }

        private  IEnumerable<BiffRecord> AddIns()
        {
            var allWorksheets = Enumerable.OfType<Worksheet>(context.Workbook.Sheets);
            var allCells = Enumerable.SelectMany(allWorksheets, ws => ws.Cells.NonVolatileCells);
            var cellsWithFormula = Enumerable.Where(allCells, c => c.Expression != null);
            var topExppressions = Enumerable.Select(cellsWithFormula, c => c.Expression);
            var allExpressions2 = Enumerable.Union(Enumerable.SelectMany(topExppressions, e => e.GetSubExpressions()),
                                                   topExppressions);
            var allFunctionInvokeExpression = Enumerable.OfType<FunctionInvokeExpression>(allExpressions2);
            var allFuctions =
                Enumerable.OfType<AddInFunction>(Enumerable.Select(allFunctionInvokeExpression, f => f.Function));
            Assembly assembly = typeof(MathFunctions).Assembly;
            var allCustomFunctions = Enumerable.Distinct(Enumerable.Where(allFuctions, f => f.AddIn.Assembly != assembly));
            var addins = new Dictionary<AddIn, List<Function>>();
            foreach (var fn in allCustomFunctions)
            {
                List<Function> functions;
                if (!addins.ContainsKey(fn.AddIn))
                {
                    functions = new List<Function>();
                    addins.Add(fn.AddIn, functions);
                }
                else
                {
                    functions = addins[fn.AddIn];
                }
                functions.Add(fn);
            }
            foreach (var keypair in addins)
            {
                yield return new SupBookRecord(keypair.Key);
                ushort index = 0;
                foreach (var fn in keypair.Value)
                    yield return new ExternNameRecord(fn, index++);
            }
        }


        private  IEnumerable<BiffRecord> Lbl()
        {
            foreach (DefinedName definedName in context.Workbook.DefinedNames)
            {
                yield return new LblRecord(context, definedName);
            }
            var cells = Enumerable.SelectMany(Enumerable.OfType<Worksheet>(context.Workbook.Sheets), sh => sh.GetCells());
            cells = Enumerable.Where(cells, c => c.Expression != null);
         //   Debug.WriteLine(string.Join("|", Enumerable.Select(cells, c => c.FullLabel)));
            var expressions = Enumerable.Select(cells, c => c.Expression);
            expressions =  Enumerable.SelectMany(expressions, ExpressionHelper.GetAllExpressions);
            var nameExpressions = Enumerable.OfType<NameExpression>(expressions);

            var expressionNames = new OpenLS.Spreadsheet.Collections.HashSet<string>(
                Enumerable.Distinct(Enumerable.Select(nameExpressions, ne => ne.Name)));
            var names = new Collections.HashSet<string>(Enumerable.Select(context.Workbook.DefinedNames, dn => dn.Name));
            var expressionNames2 = Enumerable.Where(expressionNames, ne => !names.Contains(ne));
            foreach (var name in expressionNames2)
            {
                yield return new LblRecord(context, name);
            }
            foreach (
                var range in
                    Enumerable.Select(
                        Enumerable.Where(Enumerable.OfType<Worksheet>(context.Workbook.Sheets),
                                         sh => sh.AutoFilter != null), ws => ws.AutoFilter.Range))
            {
                yield return LblRecord.CreateAutoFilterRecord(context, range);
            }
        }

        public  IEnumerable<XLUnicodeRichExtendedString> GetXlStrings()
        {
            IEnumerable<Cell> cells = Enumerable.SelectMany(Enumerable.OfType<Worksheet>(context.Workbook.Sheets),
                                             ws => ws.Cells.NonVolatileCells);
            cells = Enumerable.Where(cells, cell => cell.Value is string)
                ;
            foreach (var s in Enumerable.Select(cells, cell => (string)cell.Value))
                context.Workbook.SharedStrings.GetIndex(s);
            foreach (var v in context.Workbook.SharedStrings.GetUniqueItems())
            {
                if (v is string)
                {
                    string str = (string)v;
                    XLUnicodeRichExtendedString newString = (XLUnicodeRichExtendedString)str;
                    context.StringTable.Add(newString, context.StringTable.Count);
                    yield return newString;
                }
                else
                {
                    throw new NotImplementedException();
                }

            }
        }



        private  IEnumerable<BiffRecord> SupBook()
        {
            yield return new SupBookRecord() { IsSelfReferencing = true };
         //   foreach (var link in context.Workbook.ExternalLinks)
                yield return new ExternSheetRecord(context, null);
        }

        private  IEnumerable<object> Metadata()
        {
            //\\
            yield break;
        }

        private  IEnumerable<object> BundleSheets()
        {
            foreach (var sheet in context.Workbook.Sheets)
            {
                if (sheet is Chartsheet)
                    continue;  ///\\ignored for now
                if (!(sheet is Worksheet))
                    throw new NotImplementedException();
                BoundSheetRecord result = BoundSheetRecord.FromWorksheet((Worksheet)sheet);
                context.SheetToBoundSheet[sheet] = result;
                yield return result;
            }
        }

        private  IEnumerable<object> Formatting()
        {
            yield return Fonts();
            yield return Formats();
            yield return XfAndXfExt();
            yield return Dxfs();
            yield return Styles();
            yield return TableStyles();
            if (context.Workbook.StyleSheet.IndexedColors.Count > 0)
                yield return new PaletteRecord(context.Workbook.StyleSheet);
            //[ClrtClient]
        }
        IEnumerable<BiffRecord> TableStyles()
        {
            var allTables = Enumerable.ToList(Enumerable.SelectMany(
                Enumerable.OfType<Worksheet>(this.context.Workbook.Sheets), ws => ws.Tables));
            if (allTables.Count == 0)
                yield break;
            yield return new TableStylesRecord("TableStyleMedium2", "PivotStyleLight16");
        }

        static IEnumerable<CellStyle> GetAllStyles(Workbook workbook)
        {
            Collections.HashSet<CellStyle> workbookStyles = new Collections.HashSet<CellStyle>(workbook.Styles);
            Collections.HashSet<CellStyle> alreadyReturned = new Collections.HashSet<CellStyle>();
             foreach (var style in workbook.BuiltInStyles)
             {
                 CellStyle toYield = Enumerable.FirstOrDefault(workbookStyles, s => s.SameName(style));
                 if (toYield == null)
                     toYield = style;
                yield return toYield;
                 alreadyReturned.Add(toYield);
             }
             foreach (var style in workbookStyles)
             {
                 if (!alreadyReturned.Contains(style))
                     yield return style;
             }
        }
#if DEBUG2
        public static  void CheckWorkbook(Workbook workbook)
        {
             var styleFormattingRecords = Enumerable.ToList(Enumerable.Select(GetAllStyles(workbook), s => s.Record));
            // Debug.Write(string.Join("|", Enumerable.Select (styleFormattingRecords, r => r.Style.Name)));
            foreach (var cell in workbook.GetCells())
            {
                var record = cell.FormattingRecord;
                if (record != null && record.Parent != null)
                {
                    var parent = record.Parent;
                    var style = record.Parent.Style;
                    string styleName = record.Parent.Style.Name;
                    //var otherStyle = Enumerable.First(workbook.Styles, s => s.Name == styleName);
                    //Contract.Assert((otherStyle != null));
                    //Contract.Assert((otherStyle == style));
                    var otherStyle = Enumerable.First(GetAllStyles(workbook), s => s.Name == styleName);
                    Contract.Assert((otherStyle != null));
                    Contract.Assert((otherStyle == style));
                    Contract.Assert(styleFormattingRecords.Contains(record.Parent) ||
                                    IsSkippedName(record.Parent.Style.Name));
                }
            }
        }
#endif

        static bool IsSkippedName(string s)
         {
             return false;
            switch (s)
            {
                //\\ case "Normal":
                case "Comma":
                case "Comma [0]":
                case "Currency":
                case "Currency [0]":
                    return true;
                default:
                    return false;
            }

        }
        private  IEnumerable<BiffRecord> Styles()
        {
            foreach (var style in GetAllStyles(context.Workbook))
            {
                if (IsSkippedName(style.Name))
                    continue;
                BiffStyleRecord result = BiffStyleRecord.Create(style, context);
                yield return result;
            }
        }

        private  IEnumerable<BiffRecord> Dxfs()
        {
            yield break;
        }
        BiffWriterWorkbookContext context;
        private  IEnumerable<XFRecord> Xfs()
        {
#if DEBUG
            this.context.Workbook.CheckFormattingRecords();
            //CheckWorkbook(this.context.Workbook);
#endif
            //  var records = Enumerable.Select(context.Workbook.StyleSheet.StyleRecords, r => r);
            //  records = Enumerable.Union(records, Enumerable.Select(GetAllStyles(context.Workbook), style => style.Record));
            //  records = Enumerable.Distinct(records);
            IEnumerable<FormattingRecord> records;
            records = GetStyleStyleRecords(context.Workbook);
            foreach (var r in records)
            {
                
                XFRecord xfRecord = XFRecord.FromFormattingRecord(r, context);
                context.AddFormattingRecord(r);
                yield return xfRecord;
            }
#if DEBUG
            this.context.Workbook.CheckFormattingRecords();
#endif
            records = GetCellStyleRecords(context.Workbook);
            foreach (var r in records)
            {
                XFRecord xfRecord = XFRecord.FromFormattingRecord(r, context);
                context.AddFormattingRecord(r);
                yield return xfRecord;
            }
        }
         IEnumerable<BiffRecord> XfAndXfExt()
        {
            var xfs = Enumerable.ToList(Xfs());
            var xfExts = Enumerable.Select(xfs, xf => xf.ExtRecord);
            var xfcrcRecord = new XfcrcRecord(xfs);
            foreach (var item in xfs)
                yield return item;
            yield return xfcrcRecord;
            foreach (var item in Enumerable.Where(xfExts, r => r != null))
                yield return item;

        }

        private  IEnumerable<BiffRecord> Formats()
        {
            foreach (var i in new ushort[] { 5, 6, 7, 8, 42, 46, 44, 43 })
            {
                var s = StandardFormats.GetFormat(i).Code.Replace("$", "\"$\"");
                context.AddCodeId(s, i);
                yield return new FormatRecord(s, i); //\\loc
            }
            var records = Enumerable.Union(GetStyleStyleRecords(context.Workbook), GetCellStyleRecords(context.Workbook));
             var codes = Enumerable.Distinct(Enumerable.Select(records, r => r.FormatCode));
            // var codes = Enumerable.Distinct(Enumerable.Select(context.Workbook.StyleSheet.GetStyleRecords(), r => r.FormatCode));
            foreach (var code in codes)
            {
                ushort codeId = context.EnsureCodeId(code);
                yield return new FormatRecord(code, codeId);
            }
        }

        private  IEnumerable<BiffRecord> Fonts()
        {
            var documentFonts = Enumerable.Select(GetRecords(context.Workbook), record => record.Font);
            var comments = Enumerable.Where(Enumerable.Select(context.Workbook.GetCells(), c => c.Comment),
                                                c => c != null && c.Text != null);
            var commentRuns = Enumerable.SelectMany(comments, c => c.Text.Runs);
            var commentFonts = Enumerable.Select(commentRuns, r => TxoRuns.GetFont(r));
#if DEBUG
            var commentFonts2 = new List<Font>(Enumerable.Select(commentRuns, r => TxoRuns.GetFont(r)));
            foreach (var item in commentFonts2)
                Debug.WriteLine(item);
#endif
            documentFonts = Enumerable.Where(documentFonts, f => f != null);
            FontFamily defaultFontName = context.Workbook.Theme != null ? context.Workbook.Theme.FontScheme.MinorFont.Latin : new FontFamily("Arial");
            List<Font> preambule = new List<Font>()
                                       {
                                           new Font(defaultFontName),
                                           new Font(defaultFontName,
                                                    f => f.FontWeight = FontWeights.Bold),
                                           new Font(defaultFontName,
                                                    f => f.FontStyle = FontStyles.Italic),
                                           new Font(defaultFontName, f =>{f.FontWeight=FontWeights.Bold;f.FontStyle=FontStyles.Italic; })
                                       };

            var allFonts = Enumerable.Union(preambule, Enumerable.Union(documentFonts, commentFonts));
            var fonts = new List<Font>(Enumerable.Distinct(allFonts));
           //\\ fonts.Insert(4, fonts[0]);
            foreach (var font in fonts)
            {
                context.AddFont(font);
            }
            foreach (var font in fonts)
            {
                yield return new FontRecord(font, context);
            }
        }

        private  IEnumerable<FormattingRecord> GetCellStyleRecords(Workbook workbook)
        {
            List<FormattingRecord> result = new List<FormattingRecord>(workbook.StyleSheet.GetStyleRecords());
#if DEBUG
            foreach (Worksheet worksheet in Enumerable.OfType<Worksheet>(workbook.Sheets))
            {
                foreach (Column column in worksheet.Columns)
                {
                    if (column.FormattingRecord != null)
                        Contract.Assert(result.Contains(column.FormattingRecord));
                }
            }
#endif
            return result;
        }
        public  IEnumerable<FormattingRecord> GetRecords(Workbook workbook)
        {
            var result =
                Enumerable.Distinct(Enumerable.Union(GetCellStyleRecords(workbook), GetStyleStyleRecords(workbook)));
            return result;
        }
        public  IEnumerable<FormattingRecord> GetStyleStyleRecords(Workbook workbook)
        {
            var allStyleRecords = Enumerable.ToList(Enumerable.Select(GetAllStyles(workbook), style => style.Record));
            Dictionary<FormattingRecord, FormattingRecord> table = new Dictionary<FormattingRecord, FormattingRecord>();
            {
                foreach (var record in allStyleRecords)
                {
                    if (table.ContainsKey(record))
                    {
                        var otherRecord = table[record];
                        Debug.WriteLine("Conflict" + record.GetHashCode() + " " + table[record].GetHashCode());
                    }
                    else
                    {
                        table.Add(record, record);
                    }
                }
            }
            return Enumerable.OfType<FormattingRecord>(Enumerable.Distinct(Enumerable.Select(GetAllStyles(workbook), style => style.Record)));
        }

#if DEBUG
        private  void TestRecords(Workbook workbook)
        {
          /*  var records1 = new List<FormattingRecord>(Enumerable.Select(workbook.BuiltInStyles, style => style.Record));
            var records2 = new List<FormattingRecord>(Enumerable.Select(workbook.BuiltInStyles, style => style.Record));
            Contract.Assert(records1.Count == records2.Count);*/
        }
#endif

        private  IEnumerable<BiffRecord> Protection()
        {
            yield return new WindowProtectRecord();
            yield return new ProtectRecord();
            yield return new PasswordRecord();
            yield return new Prot4RevRecord();
            yield return new Prot4RevpassRecord();
        }

        private  IEnumerable<object> Interface()
        {
            yield return new InterfaceHdrRecord();
            yield return new MMSRecord();
            yield return new InterfaceEndRecord();
        }
    }
}
